package net.openvision.music.composition;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import net.openvision.util.Constraint;

public class Chord implements ElementClass {

	public static final short C_MAJOR = (short)0x0091;
	

	private ElementValue chord;
	private List<ValueClass> bases;

	private void _init(Iterable<Byte> values) {
		List<Byte> list = new ArrayList<Byte>();
		for (byte i : values) {
			ValueClass vc = new OctaveValueClass(i);
			for (byte v : vc) {
				list.add(v);
			}
		}
		chord = new ElementValue(list);
	}

	public Chord(short id, byte... bases) {
		this.bases = new ArrayList<ValueClass>();
		for (byte b : bases) {
			this.bases.add(new OctaveValueClass(b));
		}
		List<Byte> values = new ArrayList<Byte>();
		short t = (short) 0x0001;
		for (byte i = 0; i < 11; i++) {
			if ((id & t) > 0) {
				values.add(i);
			}
			t = (short) (t << 1);
		}
		_init(values);
	}

	public Chord(Byte... chord) {
		bases = new ArrayList<ValueClass>();
		bases.add(new OctaveValueClass(chord[0]));
		_init(Arrays.asList(chord));
	}
	
	public Chord(Byte[] chord, Byte[] bases) {
		this.bases = new ArrayList<ValueClass>();
		for (byte b:bases) {
			this.bases.add(new OctaveValueClass(b));
		}
		_init(Arrays.asList(chord));
	}

	private Collection<ElementValue> completeValue(ElementValue element,
			Constraint<ElementValue> constraint, int index) {
		Collection<ElementValue> result = new ArrayList<ElementValue>();
		Byte prev = element.getPrevoiusValue(index);
		Byte next = element.getNextValue(index);
		int first = (prev != ElementValue.UNDEFINED) ? chord.getIndexOf(prev)
				: 0;
		int last = (next != ElementValue.UNDEFINED) ? chord.getIndexOf(next)
				: chord.getSize() - 1;
		for (int i = first; i <= last; i++) {
			ElementValue v = element.setValue(index, chord.getValue(i));
			if (constraint == null || constraint.holds(v))
				result.add(v);
		}
		return result;
	}

	@Override
	public boolean contains(ElementValue element) {
		return (element.getValue(0) == ElementValue.UNDEFINED || basesContains(element
				.getValue(0)))
				&& chord.contains(element);
	}

	private void completeIndex(ElementValue element,
			Collection<ElementValue> result,
			Collection<Collection<ElementValue>> tmp,
			Constraint<ElementValue> constraint, int i) {
		if (element.getValue(i) == ElementValue.UNDEFINED) {
			Iterator<ElementValue> it = result.iterator();
			while (it.hasNext()) {
				tmp.add(completeValue(it.next(), constraint, i));
				it.remove();
			}
			for (Collection<ElementValue> c : tmp) {
				result.addAll(c);
			}
			tmp.clear();
		}
	}

	private boolean basesContains(byte value) {
		for (ValueClass vc : bases) {
			if (vc.contains(value))
				return true;
		}
		return false;
	}

	private void checkElement(ElementValue element) {
		if (element.getValue(0) != ElementValue.UNDEFINED
				&& !basesContains(element.getValue(0)))
			throw new IllegalArgumentException(
					"The given element has an illegal base value of "
							+ element.getValue(0));
		else if (!contains(element))
			throw new IllegalArgumentException(
					"The given element value is not contained in the element class.");
		else if (element.getFirstIndex() < 0)
			throw new IllegalArgumentException(
					"The given element does not contain any values.");
	}

	@Override
	public Collection<ElementValue> complete(ElementValue element,
			Constraint<ElementValue> constraint) {
		checkElement(element);
		Collection<ElementValue> result = new LinkedList<ElementValue>();
		ArrayList<Collection<ElementValue>> cvs = new ArrayList<Collection<ElementValue>>();
		if (element.getValue(0) != ElementValue.UNDEFINED) {
			result.add(element);
		} else {
			int fval = element.getFirstValue();
			for (ValueClass vc : bases) {
				for (byte v : vc) {
					if (v > fval)
						break;
					result.add(element.setValue(0, v));
				}
			}
		}
		for (int i = 1; i < element.getSize(); i++) {
			completeIndex(element, result, cvs, constraint, i);
		}
		return result;
	}
}
